/*
$info$
tags: thunklibs|GL
desc: Handles glXGetProcAddress
$end_info$
*/

#define GL_GLEXT_PROTOTYPES 1
#define GLX_GLXEXT_PROTOTYPES 1

#include <GL/glx.h>
#include <GL/glxext.h>
#include <GL/gl.h>
#include <GL/glext.h>

#undef GL_ARB_viewport_array
#include "glcorearb.h"

#include <stdio.h>
#include <cstdlib>
#include <functional>
#include <string_view>
#include <unordered_map>

#include "common/Guest.h"

#include "thunkgen_guest_libGL.inl"

typedef void voidFunc();

// Maps OpenGL API function names to the address of a guest function which is
// linked to the corresponding host function pointer
const std::unordered_map<std::string_view, uintptr_t /* guest function address */> HostPtrInvokers =
    std::invoke([]() {
#define PAIR(name, unused) Ret[#name] = reinterpret_cast<uintptr_t>(GetCallerForHostFunction(name));
        std::unordered_map<std::string_view, uintptr_t> Ret;
        FOREACH_internal_SYMBOL(PAIR);
        return Ret;
#undef PAIR
    });

extern "C" {
  voidFunc *glXGetProcAddress(const GLubyte *procname) {
    auto Ret = fexfn_pack_glXGetProcAddress(procname);
    if (!Ret) {
      return nullptr;
    }

    auto TargetFuncIt = HostPtrInvokers.find(reinterpret_cast<const char*>(procname));
    if (TargetFuncIt == HostPtrInvokers.end()) {
      std::string_view procname_s { reinterpret_cast<const char*>(procname) };
      // If glXGetProcAddress is querying itself, then we can just return itself.
      // Some games do this for unknown reasons.
      if (procname_s == "glXGetProcAddress" ||
          procname_s == "glXGetProcAddressARB") {
        return reinterpret_cast<voidFunc *>(glXGetProcAddress);
      }

      // Extension found in host but not in our interface definition => Not fatal but warn about it
      // Some games query leaked GLES symbols but don't use them
      // glFrustrumf : ES 1.x function
      //  - Papers, Please
      //  - Dicey Dungeons
      fprintf(stderr, "glXGetProcAddress: not found %s\n", procname);
      return nullptr;
    }

    LinkAddressToFunction((uintptr_t)Ret, TargetFuncIt->second);
    return Ret;
  }

  voidFunc *glXGetProcAddressARB(const GLubyte *procname) {
    return glXGetProcAddress(procname);
  }
}

// libGL.so must pull in libX11.so as a dependency. Referencing some libX11
// symbol here prevents the linker from optimizing away the unused dependency
auto implicit_libx11_dependency = XSetErrorHandler;

LOAD_LIB(libGL)
